<?php

require_once ROOT . '/libs/posql/posql.pack.php';
require_once dirname(__FILE__) . '/posql.reflector.php';;

class DibiPoSqlDriver extends DibiObject
        implements IDibiDriver, IDibiResultDriver {
    /**
     * @var Posql Connection to the PoSql database
     */
    private $connection = null;
    private $affectedRows = false;
    private $resultSet = null;
    private $resultFrom = null;
    private $reflector = null;
    
    public function connect(array &$config) {
        if (isset($config['resource'])) {
            $this->connection = $config['resource'];
        } else {
            if (!is_writable(ROOT . '/db')) {
                throw new DibiDriverException(
                        'The db directory is not writable', 1);
            }
            $dbname = $config['database'];
            $this->connection = new Posql(ROOT . "/db/$dbname.posql");
            if ($this->connection->isError()) {
                throw new DibiDriverException('Error: ' .
                        join(', ', $this->db->getErrors()), 2);
            }
            if (isset($config['charset'])) {
                if ($config['charset'] == 'utf8') {
                    $config['charset'] = 'UTF-8';
                }
                $this->connection->setCharset($config['charset']);
            }
        }
    }
    
    public function disconnect() {
         $this->connection = null;
    }
    
    public function query($sql) {
        $result = $this->connection->query($sql);
        if ($this->connection->isError()) {
            throw new DibiDriverException('Cannot fetch query result: ' .
                    join(', ', $this->connection->getErrors()), 4, $sql);
        } elseif ($result instanceof Posql_Statement) {
            $this->affectedRows = false;
            $parsed = $this->connection->parseQueryImplements($sql);
            if (isset($parsed['from'])) {
                return $this->createResultDriver($result, $parsed['from']);
            }
            return $this->createResultDriver($result);
        }
        $this->affectedRows = $result;
    }
    
    public function getInfo() {
        throw new DibiNotSupportedException(
                'The PoSql does not provide any info about queries.');
    }
    
    public function getAffectedRows() {
        return $this->affectedRows;
    }
    
    public function getInsertId($sequence) {
        return $this->connection->lastInsertId();
    }
    
    public function begin($savepoint = NULL) {
        if (!is_null($savepoint)) {
            throw new DibiNotSupportedException(
                    'Save point are not supported on PoSql.');
        }
        $this->connection->beginTransaction();
    }
    
    public function commit($savepoint = NULL) {
        if (!is_null($savepoint)) {
            throw new DibiNotSupportedException(
                    'Save point are not supported on PoSql.');
        }
        $this->connection->commit();
    }
    
    public function rollback($savepoint = NULL) {
        if (!is_null($savepoint)) {
            throw new DibiNotSupportedException(
                    'Save point are not supported on PoSql.');
        }
        $this->connection->rollBack();
    }
    
    public function getResource() {
        return $this->connection;
    }
    
    public function getReflector() {
        if ($this->reflector) {
            return $this->reflector;
        }
        $this->reflector = new DibiPoSqlReflector($this);
        return $this->reflector;
    }
    
    public function createResultDriver(Posql_Statement $resource,
            array $from = array()) {
        $result = clone $this;
        $result->resultSet = $resource;
        $result->resultFrom = $from;
        return $result;
    }
    
    /********************* SQL ****************d*g**/
    
    public function escape($value, $type) {
        switch ($type) {
            case dibi::TEXT:
            case dibi::BINARY:
                return "'" . $this->connection->escape($value) . "'";
            case dibi::IDENTIFIER:
                return $value; // escaping identifiers isn't supported by PoSQL
            case dibi::BOOL:
                return $value ? 'TRUE' : 'FALSE';
            case dibi::DATE:
                return $value instanceof DateTime ?
                        $value->format("'Y-m-d'") : date("'Y-m-d'", $value);
            case dibi::DATETIME:
                return $value instanceof DateTime ?
                        $value->format("'Y-m-d H:i:s'") :
                        date("'Y-m-d H:i:s'", $value);
            default:
                throw new InvalidArgumentException('Unsupported type.');
        }
    }
    
    public function escapeLike($value, $pos) {
        $value = addcslashes(str_replace('\\', '\\\\', $value),
                "\x00\n\r\\'%_");
        return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
    }
    
    public function unescape($value, $type) {
        if ($type === dibi::BINARY) {
            return $value;
        }
        throw new InvalidArgumentException('Unsupported type.');
    }
    
    public function applyLimit(&$sql, $limit, $offset) {
        if ($limit < 0 && $offset < 1) {
            return; // no lomits, no offsets
        }
        
        $sql .= ' LIMIT ' . ($limit < 0 ? 'ALL' : (int) $limit) .
                ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
    }
    
    /********************* result set ****************d*g**/
    
    public function __destruct() {
        $this->free();
    }
    
    public function getRowCount() {
        return $this->resultSet->rowCount();
    }
    
    public function fetch($assoc) {
        return $this->resultSet->fetch($assoc ? 'assoc' : 'num');
    }
    
    public function seek($row) {
        throw new DibiNotSupportedException(
                'Cannot seek an unbuffered result set.');
    }
    
    public function free() {
        $this->resultSet = null;
    }
    
    public function getResultColumns() {
        $columns = array();
        $allColumns = array();   // columns of all used tables
        $columnsTable = array(); // column to table map
        foreach ($this->resultFrom as $tableName) {
            $tableColumns = $this->getReflector()->getColumns($tableName);
            foreach ($tableColumns as $column) {
                $columnsTable[$column['name']] = $tableName;
            }
            $allColumns = array_merge($allColumns, $tableColumns);
        }
        foreach ($this->resultSet->getColumnNames() as $columnName) {
            $type = null;
            foreach ($allColumns as $columnSpec) {
                if ($columnSpec['name'] == $columnName) {
                    $type = $columnSpec['nativetype'];
                    break;
                }
            }
            $columns[] = array(
                'name' => $columnName,
                'table' => isset($columnsTable[$columnName]) ?
                        $columnsTable[$columnName] : '',
                'fullname' => isset($columnsTable[$columnName]) ?
                        $columnsTable[$columnName] . '.' . $columnName :
                        $columnName,
                'nativetype' => $type,
                'vendor' => array()
            );
        }
        return $columns;
    }
    
    public function getResultResource() {
        return $this->resultSet;
    }
}
